/*
    SUSAN® - Sole of Unix Save ANything

   
      Copyright (C) 2011-2016 Skyatlas Co. LTD

   

   
*/
/*
 * Master Configuration routines.
 *
 * This file contains the common parts of the SUSAN configuration routines.
 *
 * Note, the configuration file parser consists of four parts
 *
 * 1. The generic lexical scanner in lib/lex.c and lib/lex.h
 *
 * 2. The generic config scanner in lib/parse_conf.c and lib/parse_conf.h.
 *    These files contain the parser code, some utility routines,
 *
 * 3. The generic resource functions in lib/res.c
 *    Which form the common store routines (name, int, string, time,
 *    int64, size, ...).
 *
 * 4. The daemon specific file, which contains the Resource definitions
 *    as well as any specific store routines for the resource records.
 *
 * N.B. This is a two pass parser, so if you malloc() a string in a "store"
 * routine, you must ensure to do it during only one of the two passes, or to
 * free it between.
 *
 * Also, note that the resource record is malloced and saved in SaveResource()
 * during pass 1. Anything that you want saved after pass two (e.g. resource
 * pointers) must explicitly be done in SaveResource. Take a look at the Job
 * resource in src/dird/dird_conf.c to see how it is done.
 *
 * Kern Sibbald, January MM
 */

#include "include/susan.h"
#include "include/jcr.h"
#include "lib/address_conf.h"
#include "lib/edit.h"
#include "lib/parse_conf.h"
#include "lib/parse_conf_state_machine.h"
#include "lib/qualified_resource_name_type_converter.h"
#include "lib/bstringlist.h"
#include "lib/ascii_control_characters.h"
#include "lib/messages_resource.h"
#include "lib/resource_item.h"
#include "lib/berrno.h"
#include "lib/util.h"

#include <algorithm>

#if defined(HAVE_WIN32)
#include "shlobj.h"
#else
#define MAX_PATH 1024
#endif

bool PrintMessage(void* sock, const char* fmt, ...)
{
  va_list arg_ptr;

  va_start(arg_ptr, fmt);
  vfprintf(stdout, fmt, arg_ptr);
  va_end(arg_ptr);

  return true;
}

ConfigurationParser::ConfigurationParser()
    : scan_error_(nullptr)
    , scan_warning_(nullptr)
    , init_res_(nullptr)
    , store_res_(nullptr)
    , print_res_(nullptr)
    , err_type_(0)
    , omit_defaults_(false)
    , r_first_(0)
    , r_last_(0)
    , r_own_(0)
    , own_resource_(nullptr)
    , resources_(0)
    , res_head_(nullptr)
    , SaveResourceCb_(nullptr)
    , DumpResourceCb_(nullptr)
    , FreeResourceCb_(nullptr)
    , use_config_include_dir_(false)
    , ParseConfigReadyCb_(nullptr)
    , parser_first_run_(true)
{
  return;
}

ConfigurationParser::ConfigurationParser(
    const char* cf,
    LEX_ERROR_HANDLER* ScanError,
    LEX_WARNING_HANDLER* scan_warning,
    INIT_RES_HANDLER* init_res,
    STORE_RES_HANDLER* StoreRes,
    PRINT_RES_HANDLER* print_res,
    int32_t err_type,
    int32_t r_first,
    int32_t r_last,
    ResourceTable* resources,
    SusanResource** res_head,
    const char* config_default_filename,
    const char* config_include_dir,
    void (*ParseConfigBeforeCb)(ConfigurationParser&),
    void (*ParseConfigReadyCb)(ConfigurationParser&),
    SaveResourceCb_t SaveResourceCb,
    DumpResourceCb_t DumpResourceCb,
    FreeResourceCb_t FreeResourceCb)
    : ConfigurationParser()
{
  cf_ = cf == nullptr ? "" : cf;
  use_config_include_dir_ = false;
  config_include_naming_format_ = "%s/%s/%s.conf";
  scan_error_ = ScanError;
  scan_warning_ = scan_warning;
  init_res_ = init_res;
  store_res_ = StoreRes;
  print_res_ = print_res;
  err_type_ = err_type;
  r_first_ = r_first;
  r_last_ = r_last;
  resources_ = resources;
  res_head_ = res_head;
  config_default_filename_ =
      config_default_filename == nullptr ? "" : config_default_filename;
  config_include_dir_ = config_include_dir == nullptr ? "" : config_include_dir;
  ParseConfigBeforeCb_ = ParseConfigBeforeCb;
  ParseConfigReadyCb_ = ParseConfigReadyCb;
  ASSERT(SaveResourceCb);
  ASSERT(DumpResourceCb);
  ASSERT(FreeResourceCb);
  SaveResourceCb_ = SaveResourceCb;
  DumpResourceCb_ = DumpResourceCb;
  FreeResourceCb_ = FreeResourceCb;
}

ConfigurationParser::~ConfigurationParser()
{
  if (res_head_) {
    for (int i = r_first_; i <= r_last_; i++) {
      if (res_head_[i - r_first_]) {
        FreeResourceCb_(res_head_[i - r_first_], i);
      }
      res_head_[i - r_first_] = nullptr;
    }
  }
}

void ConfigurationParser::InitializeQualifiedResourceNameTypeConverter(
    const std::map<int, std::string>& map)
{
  qualified_resource_name_type_converter_.reset(
      new QualifiedResourceNameTypeConverter(map));
}

std::string ConfigurationParser::CreateOwnQualifiedNameForNetworkDump() const
{
  std::string qualified_name;

  if (own_resource_ && qualified_resource_name_type_converter_) {
    if (qualified_resource_name_type_converter_->ResourceToString(
            own_resource_->resource_name_, own_resource_->rcode_,
            "::", qualified_name)) {
      return qualified_name;
    }
  }
  return qualified_name;
}

bool ConfigurationParser::ParseConfig()
{
  int errstat;
  PoolMem config_path;

  if (ParseConfigBeforeCb_) ParseConfigBeforeCb_(*this);

  if (parser_first_run_ && (errstat = RwlInit(&res_lock_)) != 0) {
    BErrNo be;
    Jmsg1(nullptr, M_ABORT, 0,
          _("Unable to initialize resource lock. ERR=%s\n"),
          be.bstrerror(errstat));
  }
  parser_first_run_ = false;

  if (!FindConfigPath(config_path)) {
    Jmsg0(nullptr, M_ERROR_TERM, 0, _("Failed to find config filename.\n"));
  }
  used_config_path_ = config_path.c_str();
  Dmsg1(100, "config file = %s\n", used_config_path_.c_str());
  bool success =
      ParseConfigFile(config_path.c_str(), nullptr, scan_error_, scan_warning_);
  if (success && ParseConfigReadyCb_) { ParseConfigReadyCb_(*this); }
  return success;
}

void ConfigurationParser::lex_error(const char* cf,
                                    LEX_ERROR_HANDLER* ScanError,
                                    LEX_WARNING_HANDLER* scan_warning) const
{
  /*
   * We must create a lex packet to print the error
   */
  LEX* lexical_parser_ = (LEX*)malloc(sizeof(LEX));
  memset(lexical_parser_, 0, sizeof(LEX));

  if (ScanError) {
    lexical_parser_->ScanError = ScanError;
  } else {
    LexSetDefaultErrorHandler(lexical_parser_);
  }

  if (scan_warning) {
    lexical_parser_->scan_warning = scan_warning;
  } else {
    LexSetDefaultWarningHandler(lexical_parser_);
  }

  LexSetErrorHandlerErrorType(lexical_parser_, err_type_);
  BErrNo be;
  scan_err2(lexical_parser_, _("Cannot open config file \"%s\": %s\n"), cf,
            be.bstrerror());
  free(lexical_parser_);
}

bool ConfigurationParser::ParseConfigFile(const char* config_file_name,
                                          void* caller_ctx,
                                          LEX_ERROR_HANDLER* scan_error,
                                          LEX_WARNING_HANDLER* scan_warning)
{
  ConfigParserStateMachine state_machine(config_file_name, caller_ctx,
                                         scan_error, scan_warning, *this);

  Dmsg1(900, "Enter ParseConfigFile(%s)\n", config_file_name);

  do {
    if (!state_machine.InitParserPass()) { return false; }

    if (!state_machine.ParseAllTokens()) {
      scan_err0(state_machine.lexical_parser_, _("ParseAllTokens failed."));
      return false;
    }

    switch (state_machine.GetParseError()) {
      case ConfigParserStateMachine::ParserError::kResourceIncomplete:
        scan_err0(state_machine.lexical_parser_,
                  _("End of conf file reached with unclosed resource."));
        return false;
      case ConfigParserStateMachine::ParserError::kParserError:
        scan_err0(state_machine.lexical_parser_, _("Parser Error occurred."));
        return false;
      case ConfigParserStateMachine::ParserError::kNoError:
        break;
    }

  } while (!state_machine.Finished());

  state_machine.DumpResourcesAfterSecondPass();

  Dmsg0(900, "Leave ParseConfigFile()\n");
  return true;
}

bool ConfigurationParser::AppendToResourcesChain(SusanResource* new_resource,
                                                 int rcode)
{
  int rindex = rcode - r_first_;

  if (!new_resource->resource_name_) {
    Emsg1(M_ERROR, 0,
          _("Name item is required in %s resource, but not found.\n"),
          resources_[rindex].name);
    return false;
  }

  if (!res_head_[rindex]) {
    res_head_[rindex] = new_resource;
    Dmsg3(900, "Inserting first %s res: %s index=%d\n", ResToStr(rcode),
          new_resource->resource_name_, rindex);
  } else {  // append
    SusanResource* last = nullptr;
    SusanResource* current = res_head_[rindex];
    do {
      if (bstrcmp(current->resource_name_, new_resource->resource_name_)) {
        Emsg2(M_ERROR, 0,
              _("Attempt to define second %s resource named \"%s\" is not "
                "permitted.\n"),
              resources_[rindex].name, new_resource->resource_name_);
        return false;
      }
      last = current;
      current = last->next_;
    } while (current);
    last->next_ = new_resource;
    Dmsg3(900, _("Inserting %s res: %s index=%d\n"), ResToStr(rcode),
          new_resource->resource_name_, rindex);
  }
  return true;
}

int ConfigurationParser::GetResourceTableIndex(int resource_type)
{
  int rindex = -1;

  if ((resource_type >= r_first_) && (resource_type <= r_last_)) {
    rindex = resource_type = r_first_;
  }

  return rindex;
}

ResourceTable* ConfigurationParser::GetResourceTable(int resource_type)
{
  ResourceTable* result = nullptr;
  int rindex = GetResourceTableIndex(resource_type);

  if (rindex >= 0) { result = &resources_[rindex]; }

  return result;
}

ResourceTable* ConfigurationParser::GetResourceTable(
    const char* resource_type_name)
{
  ResourceTable* result = nullptr;
  int i;

  for (i = 0; resources_[i].name; i++) {
    if (Bstrcasecmp(resources_[i].name, resource_type_name)) {
      result = &resources_[i];
    }
  }

  return result;
}

int ConfigurationParser::GetResourceItemIndex(ResourceItem* resource_items_,
                                              const char* item)
{
  int result = -1;
  int i;

  for (i = 0; resource_items_[i].name; i++) {
    if (Bstrcasecmp(resource_items_[i].name, item)) {
      result = i;
      break;
    }
  }

  return result;
}

ResourceItem* ConfigurationParser::GetResourceItem(
    ResourceItem* resource_items_,
    const char* item)
{
  ResourceItem* result = nullptr;
  int i = -1;

  if (resource_items_) {
    i = GetResourceItemIndex(resource_items_, item);
    if (i >= 0) { result = &resource_items_[i]; }
  }

  return result;
}

const char* ConfigurationParser::GetDefaultConfigDir()
{
#if defined(HAVE_WIN32)
  HRESULT hr;
  static char szConfigDir[MAX_PATH + 1] = {0};

  if (!p_SHGetFolderPath) {
    bstrncpy(szConfigDir, DEFAULT_CONFIGDIR, sizeof(szConfigDir));
    return szConfigDir;
  }

  if (szConfigDir[0] == '\0') {
    hr = p_SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0,
                           szConfigDir);

    if (SUCCEEDED(hr)) {
      bstrncat(szConfigDir, "\\Susan", sizeof(szConfigDir));
    } else {
      bstrncpy(szConfigDir, DEFAULT_CONFIGDIR, sizeof(szConfigDir));
    }
  }

  return szConfigDir;
#else
  return CONFDIR;
#endif
}

#ifdef HAVE_SETENV
static inline void set_env(const char* key, const char* value)
{
  setenv(key, value, 1);
}
#elif HAVE_PUTENV
static inline void set_env(const char* key, const char* value)
{
  PoolMem env_string;

  Mmsg(env_string, "%s=%s", key, value);
  putenv(strdup(env_string.c_str()));
}
#else
static inline void set_env(const char* key, const char* value) {}
#endif

bool ConfigurationParser::GetConfigFile(PoolMem& full_path,
                                        const char* config_dir,
                                        const char* config_filename)
{
  bool found = false;

  if (!PathIsDirectory(config_dir)) { return false; }

  if (config_filename) {
    full_path.strcpy(config_dir);
    if (PathAppend(full_path, config_filename)) {
      if (PathExists(full_path)) {
        config_dir_ = config_dir;
        found = true;
      }
    }
  }

  return found;
}

bool ConfigurationParser::GetConfigIncludePath(PoolMem& full_path,
                                               const char* config_dir)
{
  bool found = false;

  if (!config_include_dir_.empty()) {
    /*
     * Set full_path to the initial part of the include path,
     * so it can be used as result, even on errors.
     * On success, full_path will be overwritten with the full path.
     */
    full_path.strcpy(config_dir);
    PathAppend(full_path, config_include_dir_.c_str());
    if (PathIsDirectory(full_path)) {
      config_dir_ = config_dir;
      /*
       * Set full_path to wildcard path.
       */
      if (GetPathOfResource(full_path, nullptr, nullptr, nullptr, true)) {
        use_config_include_dir_ = true;
        found = true;
      }
    }
  }

  return found;
}

/*
 * Returns false on error
 *         true  on OK, with full_path set to where config file should be
 */
bool ConfigurationParser::FindConfigPath(PoolMem& full_path)
{
  bool found = false;
  PoolMem config_dir;
  PoolMem config_path_file;

  if (cf_.empty()) {
    /*
     * No path is given, so use the defaults.
     */
    found = GetConfigFile(full_path, GetDefaultConfigDir(),
                          config_default_filename_.c_str());
    if (!found) {
      config_path_file.strcpy(full_path);
      found = GetConfigIncludePath(full_path, GetDefaultConfigDir());
    }
    if (!found) {
      Jmsg2(nullptr, M_ERROR, 0,
            _("Failed to read config file at the default locations "
              "\"%s\" (config file path) and \"%s\" (config include "
              "directory).\n"),
            config_path_file.c_str(), full_path.c_str());
    }
  } else if (PathExists(cf_.c_str())) {
    /*
     * Path is given and exists.
     */
    if (PathIsDirectory(cf_.c_str())) {
      found = GetConfigFile(full_path, cf_.c_str(),
                            config_default_filename_.c_str());
      if (!found) {
        config_path_file.strcpy(full_path);
        found = GetConfigIncludePath(full_path, cf_.c_str());
      }
      if (!found) {
        Jmsg3(nullptr, M_ERROR, 0,
              _("Failed to find configuration files under directory \"%s\". "
                "Did look for \"%s\" (config file path) and \"%s\" (config "
                "include directory).\n"),
              cf_.c_str(), config_path_file.c_str(), full_path.c_str());
      }
    } else {
      full_path.strcpy(cf_.c_str());
      PathGetDirectory(config_dir, full_path);
      config_dir_ = config_dir.c_str();
      found = true;
    }
  } else if (config_default_filename_.empty()) {
    /*
     * Compatibility with older versions.
     * If config_default_filename_ is not set,
     * cf_ may contain what is expected in config_default_filename_.
     */
    found = GetConfigFile(full_path, GetDefaultConfigDir(), cf_.c_str());
    if (!found) {
      Jmsg2(nullptr, M_ERROR, 0,
            _("Failed to find configuration files at \"%s\" and \"%s\".\n"),
            cf_.c_str(), full_path.c_str());
    }
  } else {
    Jmsg1(nullptr, M_ERROR, 0, _("Failed to read config file \"%s\"\n"),
          cf_.c_str());
  }

  if (found) { set_env("SUSAN_CFGDIR", config_dir_.c_str()); }

  return found;
}

SusanResource** ConfigurationParser::SaveResources()
{
  int num = r_last_ - r_first_ + 1;
  SusanResource** res =
      (SusanResource**)malloc(num * sizeof(SusanResource*));

  for (int i = 0; i < num; i++) {
    res[i] = res_head_[i];
    res_head_[i] = nullptr;
  }

  return res;
}

bool ConfigurationParser::RemoveResource(int rcode, const char* name)
{
  int rindex = rcode - r_first_;
  SusanResource* last;

  /*
   * Remove resource from list.
   *
   * Note: this is intended for removing a resource that has just been added,
   * but proven to be incorrect (added by console command "configure add").
   * For a general approach, a check if this resource is referenced by other
   * resources must be added. If it is referenced, don't remove it.
   */
  last = nullptr;
  for (SusanResource* res = res_head_[rindex]; res; res = res->next_) {
    if (bstrcmp(res->resource_name_, name)) {
      if (!last) {
        Dmsg2(900,
              _("removing resource %s, name=%s (first resource in list)\n"),
              ResToStr(rcode), name);
        res_head_[rindex] = res->next_;
      } else {
        Dmsg2(900, _("removing resource %s, name=%s\n"), ResToStr(rcode), name);
        last->next_ = res->next_;
      }
      res->next_ = nullptr;
      FreeResourceCb_(res, rcode);
      return true;
    }
    last = res;
  }

  /*
   * Resource with this name not found
   */
  return false;
}

void ConfigurationParser::DumpResources(bool sendit(void* sock,
                                                    const char* fmt,
                                                    ...),
                                        void* sock,
                                        bool hide_sensitive_data)
{
  for (int i = r_first_; i <= r_last_; i++) {
    if (res_head_[i - r_first_]) {
      DumpResourceCb_(i, res_head_[i - r_first_], sendit, sock,
                      hide_sensitive_data, false);
    }
  }
}

bool ConfigurationParser::GetPathOfResource(PoolMem& path,
                                            const char* component,
                                            const char* resourcetype,
                                            const char* name,
                                            bool set_wildcards)
{
  PoolMem rel_path(PM_FNAME);
  PoolMem directory(PM_FNAME);
  PoolMem resourcetype_lowercase(resourcetype);
  resourcetype_lowercase.toLower();

  if (!component) {
    if (!config_include_dir_.empty()) {
      component = config_include_dir_.c_str();
    } else {
      return false;
    }
  }

  if (resourcetype_lowercase.strlen() <= 0) {
    if (set_wildcards) {
      resourcetype_lowercase.strcpy("*");
    } else {
      return false;
    }
  }

  if (!name) {
    if (set_wildcards) {
      name = "*";
    } else {
      return false;
    }
  }

  path.strcpy(config_dir_.c_str());
  rel_path.bsprintf(config_include_naming_format_.c_str(), component,
                    resourcetype_lowercase.c_str(), name);
  PathAppend(path, rel_path);

  return true;
}

bool ConfigurationParser::GetPathOfNewResource(PoolMem& path,
                                               PoolMem& extramsg,
                                               const char* component,
                                               const char* resourcetype,
                                               const char* name,
                                               bool error_if_exists,
                                               bool create_directories)
{
  PoolMem rel_path(PM_FNAME);
  PoolMem directory(PM_FNAME);
  PoolMem resourcetype_lowercase(resourcetype);
  resourcetype_lowercase.toLower();

  if (!GetPathOfResource(path, component, resourcetype, name, false)) {
    return false;
  }

  PathGetDirectory(directory, path);

  if (create_directories) { PathCreate(directory); }

  if (!PathExists(directory)) {
    extramsg.bsprintf("Resource config directory \"%s\" does not exist.\n",
                      directory.c_str());
    return false;
  }

  /*
   * Store name for temporary file in extramsg.
   * Can be used, if result is true.
   * Otherwise it contains an error message.
   */
  extramsg.bsprintf("%s.tmp", path.c_str());

  if (!error_if_exists) { return true; }

  /*
   * File should not exists, as it is going to be created.
   */
  if (PathExists(path)) {
    extramsg.bsprintf("Resource config file \"%s\" already exists.\n",
                      path.c_str());
    return false;
  }

  if (PathExists(extramsg)) {
    extramsg.bsprintf(
        "Temporary resource config file \"%s.tmp\" already exists.\n",
        path.c_str());
    return false;
  }

  return true;
}

void ConfigurationParser::AddWarning(const std::string& warning)
{
  warnings_ << warning;
}

void ConfigurationParser::ClearWarnings() { warnings_.clear(); }

bool ConfigurationParser::HasWarnings() const { return !warnings_.empty(); }

const BStringList& ConfigurationParser::GetWarnings() const
{
  return warnings_;
}
